/*
 * $Id: PerforceUIPlugin.java 23 2002-09-29 13:59:30Z bpruessmann $
 * 
 * Copyright (c) 2001 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 *	  David Corbin - added images, MicroPlugin implementation, static getClientName() method
 */
package net.sourceforge.p4eclipse.ui;

import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sourceforge.p4eclipse.core.IResourceStateChangeListener;
import net.sourceforge.p4eclipse.core.PerforceException;
import net.sourceforge.p4eclipse.core.PerforceSynchronizer;
import net.sourceforge.p4eclipse.core.Policy;
import net.sourceforge.p4eclipse.core.api.Client;
import net.sourceforge.p4eclipse.core.api.IChangesResult;
import net.sourceforge.p4eclipse.core.api.IClient;
import net.sourceforge.p4eclipse.core.api.IInfoResult;
import net.sourceforge.p4eclipse.core.api.IOpenedResult;
import net.sourceforge.p4eclipse.core.api.LoggingClient;
import net.sourceforge.p4eclipse.core.api.PerforceError;
import net.sourceforge.p4eclipse.core.resources.RemoteFile;
import net.sourceforge.p4eclipse.core.resources.RemoteFolder;
import net.sourceforge.p4eclipse.internal.ui.model.ChangesResultAdapterFactory;
import net.sourceforge.p4eclipse.internal.ui.model.OpenedResultAdapterFactory;
import net.sourceforge.p4eclipse.ui.model.PerforceAdapterFactory;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPluginDescriptor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.plugin.AbstractUIPlugin;

/**
  * @version 	$Revision: 23 $
  * @author		<a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
public class PerforceUIPlugin extends AbstractUIPlugin implements MicroPlugin
{
	public static final String ID        = "net.sourceforge.p4eclipse"; //$NON-NLS-1$
	public static final String NATURE_ID = ID + ".perforcenature";      //$NON-NLS-1$

	public static final String P4PORT_PREFERENCE   = ID + ".preferences.p4port"; //$NON-NLS-1$
	public static final String P4CLIENT_PREFERENCE = ID + ".preferences.p4client"; //$NON-NLS-1$
	public static final String P4USER_PREFERENCE   = ID + ".preferences.p4user"; //$NON-NLS-1$		
	public static final String CALCULATE_DIRTY_PREFERENCE = ID + ".preferences.calculate_dirty"; //$NON-NLS-1$
	public static final String DELETE_AFFECTS_CHANGELIST = ID + ".preferences.delete_affects_changelist"; //$NON-NLS-1$
	public static final String CACHE_UPD_ENABLED = ID + ".preferences.cache_upd_enabled"; //$NON-NLS-1$
	public static final String CACHE_UPD_INTERVAL_PREFERENCE = ID + ".preferences.cache_upd_interval"; //$NON-NLS-1$
	public static final String INACTIVITY_TIMEOUT = ID + ".preferences.inactivity_timeout"; //$NON-NLS-1$

	public static final String ICON_PATH_FULL = "icons/full/";             //$NON-NLS-1$
	public static final String IMG_CLEAR = "clcl16/clear_co.gif";          //$NON-NLS-1$
	public static final String IMG_ROOTELEMENT = "clcl16/depot.gif"; //$NON-NLS-1$

	public static final String IMG_REPOSITORY = "cview16/depot_view.gif"; //$NON-NLS-1$
	public static final String IMG_ERROR = "misc/error.gif"; //$NON-NLS-1$
	
	public static final String IMG_REFRESH = "clcl16/refresh.gif"; //$NON-NLS-1$
	
	public static final String IMG_CLIENT_CHANGELISTS = "clcl16/client_cls.gif"; //$NON-NLS-1$
	public static final String IMG_CLIENT_CHANGELIST = "clcl16/client_cl.gif"; //$NON-NLS-1$	
	public static final String IMG_OTHER_CHANGELISTS = "clcl16/other_cls.gif"; //$NON-NLS-1$
	public static final String IMG_OTHER_CHANGELIST = "clcl16/other_cl.gif"; //$NON-NLS-1$	
	public static final String IMG_SUBMITTED_CHANGELIST = "clcl16/submitted_cl.gif"; //$NON-NLS-1$		
	
	public static final String OVR_HASREMOTE   = "ovr/hasRemote.gif";   //$NON-NLS-1$
	public static final String OVR_OUTOFDATE   = "ovr/outOfDate.gif";   //$NON-NLS-1$	
	public static final String OVR_OPENFORADD  = "ovr/openForAdd.gif";  //$NON-NLS-1$	
	public static final String OVR_OPENFOREDIT = "ovr/openForEdit.gif"; //$NON-NLS-1$	

	public static final String OVR_OTHER_OPENFOREDIT = "ovr/otherOpenForEdit.gif"; //$NON-NLS-1$		
	
	
	private static PerforceUIPlugin instance;
	private static IClient client;
	
	private Map imageDescriptors = new HashMap();
	private List resourceStateChangeListernList = new ArrayList();
	private ClientConnectionMonitor connectionMonitor;
	
	private static boolean isClientDropped = false;
	
	/**
	  * Constructor for PerforceUIPlugin.
	  * @param descriptor
	  */
	public PerforceUIPlugin(IPluginDescriptor descriptor)
	{
		super(descriptor);
		
		Policy.assertNull(instance);
		instance = this;	
		MicroPluginFactory.setPlugin(this);
	}

	/**
	  * Returns the plugin's singleton instance.
	  */
	public static PerforceUIPlugin getPlugin()
	{
		Policy.assertNotNull(instance);
		return instance;
	}
	
	
	/**
	  * Returns the plugin's synchronizer singleton instance.
	  */	
	public static PerforceSynchronizer getSynchronizer()
	{
		return PerforceSynchronizer.getInstance();
	}

	/**
	  * Answers the repository provider type id for the perforce plugin.
	  */
	public static String getTypeId() 
	{
		return NATURE_ID;
	}

	/**
	  * Returns the plugin's perforce client instance.
	  */
	public static synchronized IClient getClient()
	{
		return getClient(false);
	}
		
	/**
	  * Returns the plugin's perforce client instance.
	  */
	public static synchronized IClient getClient(boolean silent)
	{
		if (client == null || client.dropped())
		{
			IPreferenceStore store = getPlugin().getPreferenceStore();

			client = new LoggingClient();
			((LoggingClient)client).setInfoPrintStream(new PrintStream(new ConsoleOutputStream(1)));
			((LoggingClient)client).setErrorPrintStream(new PrintStream(new ConsoleOutputStream(0)));
			
			client.setPerforcePort(store.getString(P4PORT_PREFERENCE));
			client.setPerforceClient(store.getString(P4CLIENT_PREFERENCE));
			client.setPerforceUser(store.getString(P4USER_PREFERENCE));

			client.setProtocol("tag", ""); //$NON-NLS-1$ //$NON-NLS-2$ 
			client.setProtocol("specstring", ""); //$NON-NLS-1$ //$NON-NLS-2$ 			
			
			client.init();			
			
			PerforceError error = client.getPerforceError();
			if (error.isFatal())
			{
				final IStatus status = new Status(IStatus.ERROR, NATURE_ID, error.getGeneric(), error.fmt(), null);
				if (!silent && !isClientDropped)
				{
					isClientDropped = true;
					Display.getDefault().asyncExec(new Runnable()
					{
						public void run()
						{
							ErrorDialog.openError(null, "Perforce Error", "Failed to connect.\nPlease check your settings (Workbench->Preferences->Team->Perforce).", status);
						}
					});
				}
			}
			else
			{				
				IInfoResult infoResult = client.info();
				if (infoResult.getClientRoot() == null)
				{
					final IStatus status = new Status(IStatus.ERROR, NATURE_ID, 0, "Unknown client name.", null);
					if (!silent && !isClientDropped)
					{
						isClientDropped = true;
						Display.getDefault().asyncExec(new Runnable()
						{
							public void run()
							{
								ErrorDialog.openError(null, "Perforce Error", "Failed to connect.\nPlease check your settings (Workbench->Preferences->Team->Perforce).", status);
							}
						});	
					}
				}
			}
		}
		
		if (getPlugin().connectionMonitor != null)
		{
			getPlugin().connectionMonitor.resetLastUse();
		}
		return client;
	}

	public static synchronized void releaseClient()
	{
		if (client != null)		
		{
			synchronized (client)
			{
				client.dispose();
				client = null;
			}
		}
	}

	public static IWorkbenchPage getActivePage() 
	{
		IWorkbenchWindow window = getPlugin().getWorkbench().getActiveWorkbenchWindow();
		
		if (window == null) 
			return null;
		else
			return window.getActivePage();
	}

	
	/*
	 * @see Plugin#startup()
	 */
	public void startup() 
		throws CoreException
	{
		super.startup();		
		Policy.localize("net.sourceforge.p4eclipse.ui.messages"); //$NON-NLS-1$
		
		PerforceSynchronizer.startup();
				
		PerforceAdapterFactory factory = new PerforceAdapterFactory();
		Platform.getAdapterManager().registerAdapters(factory, RemoteFolder.class);
		Platform.getAdapterManager().registerAdapters(factory, RemoteFile.class);
		
		ChangesResultAdapterFactory.newInstance().register();
		OpenedResultAdapterFactory.newInstance().register();
	
		connectionMonitor = new ClientConnectionMonitor();
		new Thread(connectionMonitor).start();	
		
		initializeImages();
	}
	
	/*
	 * @see Plugin#shutdown()
	 */
	public void shutdown() 
		throws CoreException
	{
		connectionMonitor.shutdown();
		
		PerforceDecorator.shutdown();
		PerforceSynchronizer.shutdown();
		
		super.shutdown();
	}
	
	public void resetClient()
	{
		releaseClient();
		isClientDropped = false;
		
		if (!getClient().dropped())
		{		
			PerforceSynchronizer.getInstance().reset();	
		}
	}

	public static void log(IStatus status)
	{
		getPlugin().getLog().log(status);
	}

	public static void logError(String message, Throwable throwable) 
	{
		getPlugin().getLog().log(new Status(IStatus.ERROR, PerforceUIPlugin .ID, IStatus.ERROR, message, throwable));
	}
	
	private void initializeImages()
	{
		URL baseURL = getDescriptor().getInstallURL();
		
		createImageDescriptor(IMG_CLEAR, baseURL);
		createImageDescriptor(IMG_ROOTELEMENT, baseURL);
		createImageDescriptor(IMG_ERROR, baseURL);
		createImageDescriptor(IMG_REPOSITORY, baseURL);
		createImageDescriptor(IMG_REFRESH, baseURL);
		
		createImageDescriptor(IMG_CLIENT_CHANGELISTS, baseURL);
		createImageDescriptor(IMG_CLIENT_CHANGELIST, baseURL);		
		createImageDescriptor(IMG_OTHER_CHANGELISTS, baseURL);
		createImageDescriptor(IMG_OTHER_CHANGELIST, baseURL);
		createImageDescriptor(IMG_SUBMITTED_CHANGELIST, baseURL);		
		
		createImageDescriptor(OVR_HASREMOTE, baseURL);
		createImageDescriptor(OVR_OUTOFDATE, baseURL);		
		createImageDescriptor(OVR_OPENFORADD, baseURL);
		createImageDescriptor(OVR_OPENFOREDIT, baseURL);		
		
		createImageDescriptor(OVR_OTHER_OPENFOREDIT, baseURL);		
	}	
	
	protected void createImageDescriptor(String id, URL baseURL) 
	{
		URL url = null;
		try 
		{
			url = new URL(baseURL, ICON_PATH_FULL + id);
		} 
		catch (MalformedURLException e) 
		{
		}
		
		ImageDescriptor desc = ImageDescriptor.createFromURL(url);
		imageDescriptors.put(id, desc);
	}	

	public ImageDescriptor getImageDescriptor(String id) 
	{
		return (ImageDescriptor)imageDescriptors.get(id);
	}
	
	/*
	 * @see AbstractUIPlugin#initializeDefaultPreferences(IPreferenceStore)
	 */
	protected void initializeDefaultPreferences(IPreferenceStore store)
	{
		IClient client = Client.newInstance();
		store.setDefault(P4PORT_PREFERENCE, client.getPerforcePort());
		store.setDefault(P4CLIENT_PREFERENCE, client.getPerforceClient());
		store.setDefault(P4USER_PREFERENCE, client.getPerforceUser());
		
		store.setDefault(CALCULATE_DIRTY_PREFERENCE, false);
		store.setDefault(DELETE_AFFECTS_CHANGELIST, true);
		store.setDefault(CACHE_UPD_ENABLED, false);
		store.setDefault(CACHE_UPD_INTERVAL_PREFERENCE, 120);
		store.setDefault(INACTIVITY_TIMEOUT, 15);
	}

	public IWorkspaceRoot getWorkspaceRoot()
	{
		return ResourcesPlugin.getWorkspace().getRoot();
	}
	
	public void addResourceStateChangeListener(IResourceStateChangeListener listener)
	{
		resourceStateChangeListernList.add(listener);
	}
	
	public void removeResourceStateChangeListener(IResourceStateChangeListener listener)
	{
		resourceStateChangeListernList.remove(listener);
	}	
	
	public void broadcastResourceStateChanges(final IResource[] resources)
	{
		for (int i = 0, length = resourceStateChangeListernList.size(); i < length; i++)
		{
			final IResourceStateChangeListener listener = 
				(IResourceStateChangeListener)resourceStateChangeListernList.get(i);
			ISafeRunnable runnable = new ISafeRunnable()
			{
				/*
				 * @see ISafeRunnable#handleException(Throwable)
				 */
				public void handleException(Throwable exception)
				{
				}

				/*
				 * @see ISafeRunnable#run()
				 */
				public void run() 
					throws Exception
				{
					listener.resourceStateChanged(resources);
				}
			};

			Platform.run(runnable);
		}
	}

	static public String getClientName()
	{
		IInfoResult infoResult = PerforceUIPlugin.getClient().info();			
		String clientName = infoResult.getClientName();
		return clientName;
	}


	private class ClientConnectionMonitor implements Runnable
	{
		private Thread ownerThread;
		private boolean shutdown = false;
		private long lastUse;
		
		ClientConnectionMonitor()
		{
			ownerThread = Thread.currentThread();

		}
		
		public void run() 
		{
			while (!shutdown)
			{
				long sleep = PerforceUIPlugin.getPlugin().getPreferenceStore().getLong(PerforceUIPlugin.INACTIVITY_TIMEOUT) * 1000;
				
				waitForTimeout(sleep);
				
				if (lastUse + sleep < System.currentTimeMillis() && client != null)
				{
					PerforceUIPlugin.releaseClient();
				}
			}
		}

		private void waitForTimeout(long sleep)
		{
			try 
			{
				synchronized (this)
				{
					wait(sleep);
				}
			} 
			catch(InterruptedException e) 
			{
			}
		}
		
		public void shutdown()
		{
			shutdown = true;
			if (ownerThread != null)
			{
				ownerThread.interrupt();
				try
				{
					ownerThread.join();
				}
				catch (InterruptedException e)
				{
					e = e;
				}
			}
		}		
		
		public void resetLastUse()
		{
			synchronized (this)
			{
				lastUse = System.currentTimeMillis();
				notifyAll();
			}
		}
	}
}
